
stacker_version = 2.71

TOOL.Category		= "Construction"
TOOL.Name			= "#Stacker"
TOOL.Command		= nil
TOOL.ConfigName		= ""
TOOL.NextTool		= 0
local StoolDelay	= 0.5
local MaxStack		= 30
local StackDelay	= false

TOOL.ClientConVar["freeze"]	 		= "1"
TOOL.ClientConVar["weld"]	 		= "1"
TOOL.ClientConVar["nocollide"]		= "0"
TOOL.ClientConVar["mode"] 			= "1"
TOOL.ClientConVar["dir"] 			= "1"
TOOL.ClientConVar["count"] 			= "1"
TOOL.ClientConVar["model"] 			= ""
TOOL.ClientConVar["offsetx"] 		= "0"
TOOL.ClientConVar["offsety"] 		= "0"
TOOL.ClientConVar["offsetz"] 		= "0"
TOOL.ClientConVar["rotp"] 			= "0"
TOOL.ClientConVar["roty"] 			= "0"
TOOL.ClientConVar["rotr"] 			= "0"
TOOL.ClientConVar["recalc"] 		= "0"
TOOL.ClientConVar["skin"] 			= "1"
TOOL.ClientConVar["material"] 		= "0"
TOOL.ClientConVar["worldcheck"]		= "1"
TOOL.ClientConVar["forcelimit"] 	= "0"

if CLIENT then
	language.Add("Tool_ol_stacker_name","Stacker")
	language.Add("Tool_ol_stacker_desc","Stack props easily")
	language.Add("Tool_ol_stacker_0","Click to stack the prop you are pointing at.")
	language.Add("Undone_ol_stacker","Undone Stack")
end

function TOOL:LeftClick(trace)
	local ply = self:GetOwner()
	if CurTime() < self.NextTool and StackDelay then
		ply:ChatPrint("Wait a few seconds before using the stool again.\n")
		return
	end
	self.NextTool = CurTime()+(StoolDelay or 1)
	if !trace.Entity then return false end
	if !trace.Entity:IsValid() then return false end
	if !IsValidStackerEnt(trace.Entity) then return false end
	if CLIENT then return true end
	local freeze			= self:GetClientNumber("freeze") == 1
	local weld				= self:GetClientNumber("weld") == 1
	local nocollide			= self:GetClientNumber("nocollide") == 1
	local mode				= self:GetClientNumber("mode")
	local dir				= self:GetClientNumber("dir")
	local count				= self:GetClientNumber("count")
	local offsetx			= self:GetClientNumber("offsetx")
	local offsety			= self:GetClientNumber("offsety")
	local offsetz			= self:GetClientNumber("offsetz")
	local rotp				= self:GetClientNumber("rotp")
	local roty				= self:GetClientNumber("roty")
	local rotr				= self:GetClientNumber("rotr")
	local skin				= self:GetClientNumber("skin")
	local material			= self:GetClientNumber("material") == 1
	local worldcheck		= self:GetClientNumber("worldcheck") == 1
	local forcelimit 		= self:GetClientNumber("forcelimit")
	local recalc			= self:GetClientNumber("recalc") == 1
	local offset			= Vector(offsetx,offsety,offsetz)
	local rot				= Angle(rotp,roty,rotr)
	local ent				= trace.Entity
	local newvec			= ent:GetPos()
	local newang			= ent:GetAngles()
	local lastent			= ent
	local stackedents		= {}
	if count > (MaxStack or 30) and (MaxStack or 30) > 0 then
		ply:ChatPrint("Stacker : You can not stack more then "..tostring(MaxStack).." props at a time!\n")
		count = MaxStack
	end
	undo.Create("ol_stacker")
	local stackdir,height,thisoffset
	for i=1, count, 1 do
		if (!self:GetSWEP():CheckLimit("props")) then break end
		if i == 1 or (mode == 2 and recalc) or (mode == 3 and recalc) then
			stackdir,height,thisoffset = self:OLStackerCalcPos(lastent,mode,dir,offset,ply)
		end
		newvec = newvec+stackdir*height+thisoffset
		newang = newang+rot
		local bFound = false
		for k,v in pairs(ents.FindInSphere(newvec,1)) do
			if v:IsValid() and v != lastent and IsValidStackerEnt(v) and v:GetPos() == newvec and v != self.GhostEntity then
				bFound = true
			end
		end
		if bFound then break end
		newent = ents.Create(ent:GetClass())
		newent:SetModel(ent:GetModel())
		newent:SetColor(ent:GetColor())
		if skin == 1 then
			newent:SetSkin(ent:GetSkin())
		end
		newent:SetPos(newvec)
		newent:SetAngles(newang)
		newent:Spawn()
		if material then
			newent:SetMaterial(ent:GetMaterial())
		end
		if freeze then
			newent:GetPhysicsObject():EnableMotion(false)
		end
		if weld then
			local weldent = constraint.Weld(lastent,newent,0,0,forcelimit)
			undo.AddEntity(weldent)
		end
		if nocollide then
			local nocollideent = constraint.NoCollide(lastent,newent,0,0)
			undo.AddEntity(nocollideent)
		end
		if IsInWorldStacker(newent) and worldcheck then
			newent:Remove()
			ply:ChatPrint("Stacker : Prop spawned in world, Disable CheckInWorld to stack anyway.\n")
			break
		end
		lastent = newent
		undo.AddEntity(newent)
		ply:AddCount("props",newent)
		ply:AddCleanup("props",newent)
		newent:SetNetworkedString("Owner",ply:Nick())
		table.insert(stackedents,newent)
	end
	undo.SetPlayer(ply)
	undo.Finish()
	hook.Call("PlayerStacked",GAMEMODE,ply,ent,stackedents)
	return true
end

function TOOL:OLStackerCalcPos(lastent,mode,dir,offset,ply)
	local forward = Vector(1,0,0):Angle()
	local pos = lastent:GetPos()
	local ang = lastent:GetAngles()
	local pang = ply:EyeAngles()
	local lower, upper = lastent:WorldSpaceAABB()
	local glower = lastent:OBBMins()
	local gupper = lastent:OBBMaxs()
	local stackdir = Vector(0,0,1)
	local height = math.abs(upper.z-lower.z)
	if mode == 1 then
		if dir == 1 then
			stackdir = forward:Up()
			height = math.abs(upper.z-lower.z)
		elseif dir == 2 then
			stackdir = forward:Up()*-1
			height = math.abs(upper.z-lower.z)
		elseif dir == 3 then
			stackdir = forward:Forward()
			height = math.abs(upper.x-lower.x)
		elseif dir == 4 then
			stackdir = forward:Forward()*-1
			height = math.abs(upper.x-lower.x)
		elseif dir == 5 then
			stackdir = forward:Right()
			height = math.abs(upper.y-lower.y)
		elseif dir == 6 then
			stackdir = forward:Right()*-1
			height = math.abs(upper.y-lower.y)
		end
	elseif mode == 2 then
		forward = ang
		if dir == 1 then
			stackdir = forward:Up()
			offset = forward:Up()*offset.X+forward:Forward()*-1*offset.Z+forward:Right()*offset.Y
			height = math.abs(gupper.z-glower.z)
		elseif dir == 2 then
			stackdir = forward:Up()*-1
			offset = forward:Up()*-1*offset.X+forward:Forward()*offset.Z+forward:Right()*offset.Y
			height = math.abs(gupper.z-glower.z)
		elseif dir == 3 then
			stackdir = forward:Forward()
			offset = forward:Forward()*offset.X+forward:Up()*offset.Z+forward:Right()*offset.Y
			height = math.abs(gupper.x-glower.x)
		elseif dir == 4 then
			stackdir = forward:Forward()*-1
			offset = forward:Forward()*-1*offset.X+forward:Up()*offset.Z+forward:Right()*-1*offset.Y
			height = math.abs(gupper.x-glower.x)
		elseif dir == 5 then
			stackdir = forward:Right()
			offset = forward:Right()*offset.X+forward:Up()*offset.Z+forward:Forward()*-1*offset.Y
			height = math.abs(gupper.y-glower.y)
		elseif dir == 6 then
			stackdir = forward:Right()*-1
			offset = forward:Right()*-1*offset.X+forward:Up()*offset.Z+forward:Forward()*offset.Y
			height = math.abs(gupper.y-glower.y)
		end
	elseif mode == 3 then
		forward = pang
		if dir == 1 then
			stackdir = forward:Up()
			offset = forward:Up()*offset.X+forward:Forward()*-1*offset.Z+forward:Right()*offset.Y
			height = math.abs(gupper.z-glower.z)
		elseif dir == 2 then
			stackdir = forward:Up()*-1
			offset = forward:Up()*-1*offset.X+forward:Forward()*offset.Z+forward:Right()*offset.Y
			height = math.abs(gupper.z-glower.z)
		elseif dir == 3 then
			stackdir = forward:Forward()
			offset = forward:Forward()*offset.X+forward:Up()*offset.Z+forward:Right()*offset.Y
			height = math.abs(gupper.x-glower.x)
		elseif dir == 4 then
			stackdir = forward:Forward()*-1
			offset = forward:Forward()*-1*offset.X+forward:Up()*offset.Z+forward:Right()*-1*offset.Y
			height = math.abs(gupper.x-glower.x)
		elseif dir == 5 then
			stackdir = forward:Right()
			offset = forward:Right()*offset.X+forward:Up()*offset.Z+forward:Forward()*-1*offset.Y
			height = math.abs(gupper.y-glower.y)
		elseif dir == 6 then
			stackdir = forward:Right()*-1
			offset = forward:Right()*-1*offset.X+forward:Up()*offset.Z+forward:Forward()*offset.Y
			height = math.abs(gupper.y-glower.y)
		end
	end
	return stackdir, height, offset
end

function TOOL:RightClick(t)
	return self:LeftClick(t)
end

function TOOL.BuildCPanel(CPanel)
	CPanel:AddControl( "Header", { Text = "#Tool_ol_stacker_name", Description	= "#Tool_ol_stacker_desc" }  )
	local params = { Label = "#Presets", MenuButton = 1, Folder = "ol_stacker", Options = {}, CVars = {} }
		params.Options.default = {
			ol_stacker_freeze		=		0,
			ol_stacker_weld			=		0,
			ol_stacker_nocollide	=		0,
			ol_stacker_mode			=		1,
			ol_stacker_dir			=		1,
			ol_stacker_count		=		10,
			ol_stacker_offsetx		=		0,
			ol_stacker_offsety		=		0,
			ol_stacker_offsetz		=		0,
			ol_stacker_rotp			=		0,
			ol_stacker_roty			=		0,
			ol_stacker_rotr			=		0,
			ol_stacker_recalc		=		0,
			ol_stacker_skin			=		1,
			ol_stacker_material		=		0,
			ol_stacker_worldcheck	=		1,
			ol_stacker_forcelimit	=		0,
		}
		table.insert( params.CVars, "ol_stacker_freeze" )
		table.insert( params.CVars, "ol_stacker_weld" )
		table.insert( params.CVars, "ol_stacker_nocollide" )
		table.insert( params.CVars, "ol_stacker_mode" )
		table.insert( params.CVars, "ol_stacker_dir" )
		table.insert( params.CVars, "ol_stacker_count" )
		table.insert( params.CVars, "ol_stacker_offsetx" )
		table.insert( params.CVars, "ol_stacker_offsety" )
		table.insert( params.CVars, "ol_stacker_offsetz" )
		table.insert( params.CVars, "ol_stacker_rotp" )
		table.insert( params.CVars, "ol_stacker_roty" )
		table.insert( params.CVars, "ol_stacker_rotr" )
		table.insert( params.CVars, "ol_stacker_recalc" )
		table.insert( params.CVars, "ol_stacker_skin" )
		table.insert( params.CVars, "ol_stacker_material" )
		table.insert( params.CVars, "ol_stacker_worldcheck")
		table.insert( params.CVars, "ol_stacker_forcelimit" )
	CPanel:AddControl( "ComboBox", params )
	CPanel:AddControl( "Checkbox", { Label = "Freeze Props", Command = "ol_stacker_freeze" } )
	CPanel:AddControl( "Checkbox", { Label = "Weld Props", Command = "ol_stacker_weld" } )
	CPanel:AddControl( "Checkbox", { Label = "No Collide Props", Command = "ol_stacker_nocollide" } )
	CPanel:AddControl( "Checkbox", { Label = "Stack with same skin", Command = "ol_stacker_skin" } )
	CPanel:AddControl( "Checkbox", { Label = "Stack with same material", Command = "ol_stacker_material" } )
	CPanel:AddControl( "Checkbox", { Label = "Check if the stack is outside the world.", Command = "ol_stacker_worldcheck" } )
	local params = {Label = "Relative To:", MenuButton = "0", Options = {}}
	params.Options["World"] = {ol_stacker_mode = "1"}
	params.Options["Prop"] = {ol_stacker_mode = "2"}
	params.Options["Player"] = {ol_stacker_mode = "3"}
	CPanel:AddControl( "ComboBox", params )
	local params = {Label = "Stack Direction", MenuButton = "0", Options = {}}
	params.Options["Up"] = {ol_stacker_dir = "1"}
	params.Options["Down"] = {ol_stacker_dir = "2"}
	params.Options["Front"] = {ol_stacker_dir = "3"}
	params.Options["Behind"] = {ol_stacker_dir = "4"}
	params.Options["Right"] = {ol_stacker_dir = "5"}
	params.Options["Left"] = {ol_stacker_dir = "6"}
	CPanel:AddControl( "ComboBox", params )
	CPanel:AddControl( "Slider",  { Label	= "Count",
					Type	= "Integer",
					Min		= 1,
					Max		= 30,
					Command = "ol_stacker_count",
					Description = "How many props to stack."}	 )
	CPanel:AddControl( "Slider",  { Label	= "Weld Strength",
					Type	= "Integer",
					Min		= 0,
					Max		= 10000,
					Command = "ol_stacker_forcelimit",
					Description = "How strong the welds are when you have the option weld checked."})
	CPanel:AddControl( "Header", { Text = "Advanced Options", Description	= "These options are for advanced users. Leave them all default (0) if you don't understand what they do." }  )
	CPanel:AddControl( "Button",  { Label	= "Reset Advanced Options",
					Command = "olstacker_resetoffsets",
					Text = "Reset"}	 )
	CPanel:AddControl( "Slider",  { Label	= "Offset X (forward/back)",
					Type	= "Float",
					Min		= -500,
					Max		= 500,
					Command = "ol_stacker_offsetx"}	 )
	CPanel:AddControl( "Slider",  { Label	= "Offset Y (right/left)",
					Type	= "Float",
					Min		= -500,
					Max		= 500,
					Command = "ol_stacker_offsety"}	 )
	CPanel:AddControl( "Slider",  { Label	= "Offset Z (up/down)",
					Type	= "Float",
					Min		= -500,
					Max		= 500,
					Command = "ol_stacker_offsetz"}	 )
	CPanel:AddControl( "Slider",  { Label	= "Rotate Pitch",
					Type	= "Float",
					Min		= 0,
					Max		= 360,
					Command = "ol_stacker_rotp"}	 )
	CPanel:AddControl( "Slider",  { Label	= "Rotate Yaw",
					Type	= "Float",
					Min		= 0,
					Max		= 360,
					Command = "ol_stacker_roty"}	 )
	CPanel:AddControl( "Slider",  { Label	= "Rotate Roll",
					Type	= "Float",
					Min		= 0,
					Max		= 360,
					Command = "ol_stacker_rotr"}	 )
	CPanel:AddControl( "Checkbox", { Label = "Stack relative to new rotation", Command = "ol_stacker_recalc", Description = "If this is checked, each item in the stack will be stacked relative to the previous item in the stack. This allows you to create curved stacks." } )
end

if CLIENT then

local function ResetOffsets(ply,command,arguments)
	LocalPlayer():ConCommand("ol_stacker_offsetx 0\n")
	LocalPlayer():ConCommand("ol_stacker_offsety 0\n")
	LocalPlayer():ConCommand("ol_stacker_offsetz 0\n")
	LocalPlayer():ConCommand("ol_stacker_rotp 0\n")
	LocalPlayer():ConCommand("ol_stacker_roty 0\n")
	LocalPlayer():ConCommand("ol_stacker_rotr 0\n")
	--LocalPlayer():ConCommand("ol_stacker_recalc 0\n")
end
concommand.Add("olstacker_resetoffsets",ResetOffsets)

end

function TOOL:UpdateGhostStack(ghost,ply,ent)
	if (!ent or !ghost) then return end
	if (!ent:IsValid() or !ghost:IsValid()) then return end
	local mode		= self:GetClientNumber("mode")
	local dir		= self:GetClientNumber("dir")
	local offsetx	= self:GetClientNumber("offsetx")
	local offsety	= self:GetClientNumber("offsety")
	local offsetz	= self:GetClientNumber("offsetz")
	local rotp		= self:GetClientNumber("rotp")
	local roty		= self:GetClientNumber("roty")
	local rotr		= self:GetClientNumber("rotr")
	local offset	= Vector(offsetx,offsety,offsetz)
	local rot		= Angle(rotp,roty,rotr)
	local skin		= self:GetClientNumber("skin")
	local material	= self:GetClientNumber("material")
	local ply		= self:GetOwner()
	local stackdir, height, thisoffset = self:OLStackerCalcPos(ent,mode,dir,offset,ply)
	local newvec	= ent:GetPos()+stackdir*height+thisoffset
	local newang	= ent:GetAngles() + rot
	ghost:SetAngles(newang)
	ghost:SetPos(newvec)
	if skin == 1 then
		ghost:SetSkin(ent:GetSkin())
	end
	if material == 1 then
		ghost:SetMaterial(ent:GetMaterial())
	end
	ghost:SetNoDraw( false )
end

Msg("\n")
Msg("Stacker Version "..stacker_version.." loaded.\n")
Msg("\n")

function TOOL:Think()
	local ply = self:GetOwner()
	local tr = utilx.GetPlayerTrace(ply,ply:GetCursorAimVector())
	local trace = util.TraceLine(tr)
	if trace.Hit then
		local newent = trace.Entity
		if newent:IsValid() and IsValidStackerEnt(newent) and (newent != self.lastent or !self.GhostEntity) then
			self:MakeGhostEntity(newent:GetModel(),Vector(0,0,0),Angle(0,0,0))
			self.lastent = newent
		end
		if (!self.lastent or !self.lastent:IsValid()) and self.GhostEntity then
			self:ReleaseGhostEntity()
		end
	end
	if self.lastent != nil and self.lastent:IsValid() then
		self:UpdateGhostStack(self.GhostEntity,ply,self.lastent)
	end
end

function IsValidStackerEnt(v)
	local a = v:GetClass()
	return (!v:IsWorld() and !v:IsPlayer() and a != "func_*" and a != "npc_*" and a != "worldspawn")
end

function IsInWorldStacker(a) -- Borrowed from Teta, and modifyed it, i hope you don't mind :(
	local maxs = a:OBBMaxs()
	local mins = a:OBBMins()
	local b = 
	{
		mins,
		Vector(maxs.x,mins.y,mins.z),
		Vector(mins.x,maxs.y,mins.z),
		Vector(mins.x,mins.y,maxs.z),
		maxs,
		Vector(mins.x,maxs.y,maxs.z),
		Vector(maxs.x,mins.y,maxs.z),
		Vector(maxs.x,maxs.y,mins.z)
	}
	for _,v in pairs(b) do
		if util.PointContents(a:LocalToWorld(v)) == CONTENTS_SOLID then
			return true
		end
	end
	return false
end
